using System;
using System.Collections.Generic;
using System.Text;
using OpenTK.Math;

namespace Dogfight2008
{
  public class Box2d
  {
    /*
     * Represents an oriented two dimensional rectangle
     * in the xy plane.
     * The rectangle is specified by giving an anchor point,
     * and the extension lengths along the two major
     * axii named A and B. In an unrotated rectangle,
     * axis A is parallell to the X axis and B to the Y axis.
     * The rotation is specified in radians, from 0 to
     * two pi, counter clockwise direction.
     */
    public Box2d(
      double x, double y,
      double axisAMin, double axisAMax,
      double axisBMin, double axisBMax,
      double rotation)
    {
      X = x;
      Y = y;
      Amin = axisAMin;
      Amax = axisAMax;
      Bmin = axisBMin;
      Bmax = axisBMax;
      Rotation = rotation;
    }

    public readonly double X, Y, Amin, Amax, Bmin, Bmax, Rotation;

    public bool Intersects(Box2d box)
    {
      // there are eight possible separating axis between two
      // boxes:
      // four from the first box,
      // four from the second box.
      // foreach such axis, we create a "ray" outgoing from the
      // corresponding edge, in the normal direction.
      // projecting the other boxes four corners onto that ray,
      // and finding out that all projections are positive,
      // means we have found a separating axis and can return
      // 'false', no intersection.
      
      // helpers:
      //  project points on rays
      //  creating rays from box
      //
      // what is a ray in our case? it is simply a start point,
      // and a direction vector. vector2 times two will be nice for this.
      
      // to compute the starting points of rays for one box, we
      // need to rotate around the anchor point and use the range information
      // available. ortho simply!
      // to compute the direction of the rays, is just a rotation of
      // either v(1,0) or v(0,1) vector
      bool one = SeparatingAxis(this, box);
      bool two = SeparatingAxis(box, this);
      bool foundSepAxis = one || two;
      return !foundSepAxis;
    }

    static bool SeparatingAxis(Box2d b1, Box2d b2)
    {
      // If i can find a projection minima that is positive,
      // I've found a separating axis.
      for (int i = 0; i < 4; i++)
      {
        Vector2 rayStart, rayDir;
        b1.GetRay(i, out rayStart, out rayDir);
        double projectionMin = 1;
        for (int j = 0; j < 4; j++)
        {
          Vector2 corner;
          b2.GetCorner(j, out corner);
          double projection = Project(rayStart, rayDir, corner);
          if (projection <= projectionMin)
            projectionMin = projection;
        }
        if (projectionMin > 0)
          return true;
      }
      return false;
    }

    static double Project(Vector2 rayStart, Vector2 rayDir, Vector2 corner)
    {
      Vector2 p = corner - rayStart;
      return Vector2.Dot(p, rayDir);
    }

    void GetRay(int j, out Vector2 rayStart, out Vector2 rayDir)
    {
      rayStart = new Vector2();
      rayDir = new Vector2();

      if (j < 2)
      {
        double sin = Math.Sin(Rotation);
        double cos = Math.Cos(Rotation);
        if (j == 0)
        {
          rayStart = v(X + cos * Amax, Y + sin * Amax);
          rayDir = v(cos, sin);
          return;
        }
        if (j == 1)
        {
          rayStart = v(X + cos * Amin, Y + sin * Amin);
          rayDir = -v(cos, sin);
          return;
        }
      }
      else
      {
        double sin = Math.Sin(Rotation + Math.PI / 2);
        double cos = Math.Cos(Rotation + Math.PI / 2);
        if (j == 2)
        {
          rayStart = v(X + cos * Bmax, Y + sin * Bmax);
          rayDir = v(cos, sin);
          return;
        }
        if (j == 3)
        {
          rayStart = v(X + cos * Bmin, Y + sin * Bmin);
          rayDir = -v(cos, sin);
          return;
        }
      }

      throw new Exception();
    }

    Vector2 v(double x, double y)
    {
      return new Vector2((float)x, (float)y);
    }

    void GetCorner(int j, out Vector2 corner)
    {
      // 0=topright
      // 1=topleft
      // 2=btmleft
      // 3=btmright
      Vector2 a = v(Math.Cos(Rotation), Math.Sin(Rotation));
      Vector2 b = v(Math.Cos(Rotation + Math.PI / 2), Math.Sin(Rotation + Math.PI / 2));
      if (j == 0)
        corner = v(X, Y) + a * (float)Amax + b * (float)Bmax;
      else if (j == 1)
        corner = v(X, Y) + a * (float)Amin + b * (float)Bmax;
      else if (j == 2)
        corner = v(X, Y) + a * (float)Amin + b * (float)Bmin;
      else if (j == 3)
        corner = v(X, Y) + a * (float)Amax + b * (float)Bmin;
      else
        throw new Exception();
    }

    private bool DoubleEq(double a, double b)
    {
      return Math.Abs(a - b) < 0.0001;
    }

    public override bool Equals(object obj)
    {
      Box2d rhs = obj as Box2d;
      if (rhs == null)
        return false;

      return
        DoubleEq(Amin, rhs.Amin) &&
        DoubleEq(Amax, rhs.Amax) &&
        DoubleEq(Bmin, rhs.Bmin) &&
        DoubleEq(Bmax, rhs.Bmax) &&
        DoubleEq(X, rhs.X) &&
        DoubleEq(Y, rhs.Y) &&
        DoubleEq(Rotation, rhs.Rotation);
    }

    public override int GetHashCode()
    {
      unchecked
      {
        return
          Amin.GetHashCode() + Amax.GetHashCode() +
          Bmin.GetHashCode() + Bmax.GetHashCode() +
          X.GetHashCode() + Y.GetHashCode() + Rotation.GetHashCode();
      }
    }

    public override string ToString()
    {
      return string.Format("Box at ({0};{1}) rotation {2} A-range [{3};{4}] B-range [{5};{6}]",
        X, Y, Rotation, Amin, Amax, Bmin, Bmax);
    }
  }
}
